package fem2.material;

import inf.jlinalg.BLAM;
import inf.jlinalg.IMatrix;
import inf.jlinalg.IVector;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;

import fem2.Element;
import fem2.MaterialModel;
import fem2.NotImplementedError;
import fem2.Variable;
import fem2.element.StructuralElement;
import fem2.enu.PropertyName;
import fem2.enu.State;
import fem2.enu.VariableName;

/**
 * abstract class for all material model which is inelastic (plasticity, damage,
 * ...)
 * 
 * @author hbui
 * 
 */
public abstract class InelasticMaterialModel extends MaterialModel {

	protected static final double sqrt_3_2 = Math.sqrt(1.5);
	protected static final double sqrt_2_3 = Math.sqrt(2.0 / 3);
	protected static final double sqrt_2 = Math.sqrt(2.0);

	protected static final int MAX_NT = 50;

	protected IMatrix De;
	protected double mu, kappa;

	private Map<Element, ArrayList<Variable>> elementVariables = new HashMap<Element, ArrayList<Variable>>();

	private int buffSize;

	public InelasticMaterialModel(double E, double nu, double thickness, double rho, State ss) {
		super(rho, thickness, ss);
		this.mu = 0.5 * E / (1 + nu);
		this.kappa = E / (3 * (1 - 2 * nu));
		switch (ss) {
		case PLANE_STRAIN:
			De = StVenantKirchhoffMM.createPlaneStrainElasticityMatrix(E, nu);
			break;
		case PLANE_STRESS:
			De = StVenantKirchhoffMM.createPlaneStressElasticityMatrix(E, nu);
			break;
		case THREE_DIMENSIONAL:
			De = StVenantKirchhoffMM.create3dElasticityMatrix(E, nu);
			break;
		default:
			throw new Error("invalid state");
		}
		/*
		 * by default, just 1 history variable is saved
		 */
		this.setHistoryBufferSize(1);
	}

	@Override
	public double getMaterialProperty(PropertyName name) {
		switch (name) {
		case SHEAR_MODULUS:
			return mu;
		case BULK_MODULUS:
			return kappa;
		default:
			throw new NotImplementedError();
		}
	}

	/**
	 * associate element with a list of variables
	 * 
	 * @param e
	 * @param vars
	 */
	public void setVariables(Element e, ArrayList<Variable> vars) {
		elementVariables.put(e, vars);
	}

	/**
	 * 
	 * @param e
	 * @return the list of variables associated to element
	 */
	public ArrayList<Variable> getVariables(Element e) {
		return elementVariables.get(e);
	}

	/**
	 * set number of history variables in the buffer
	 * 
	 * @param buffSize
	 */
	protected void setHistoryBufferSize(int buffSize) {
		this.buffSize = buffSize;
	}

	@Override
	public void Initialize(Element e) {
		int ng = e.getNumIntegrationPoints();
		int dim = e.getMeshPart().getDimension();
		int ntrs = dim * (dim + 1) / 2;
		ArrayList<Variable> vars = new ArrayList<Variable>();

		for (int i = 0; i < ng; i++) {
			Variable v = new Variable(buffSize);

			v.registerVectorVariable(VariableName.STRAIN, ntrs, false);
			v.registerMatrixVariable(VariableName.TANGENT_OPERATOR, ntrs, false);

			vars.add(v);
		}
		setVariables(e, vars);

		/*
		 * Initialize constitutive tangent for each element at each Gauss point
		 */
		for (Variable v : vars) {
			IMatrix C = v.getMatrixVariable(VariableName.TANGENT_OPERATOR);
			BLAM.copy(this.De, C);
		}
	}

	@Override
	public void InitializeSolutionStep(Element e) {
		/*
		 * compute stress at each Gauss point
		 */
		StructuralElement se = (StructuralElement) e;
		for (int k = 0; k < se.getNumIntegrationPoints(); k++) {
			double[] xi = se.getIntegrationPoint(k);
			IVector sigma = se.getStress(k);
			IVector epsilon = se.computeStrain(xi);

			/*
			 * update sigma += dsigma with dsigma=C*depsilon
			 */
			IMatrix C = getConstitutiveMatrix(e, k);
			IVector epsilonOld = getVariables(e).get(k).getVectorVariable(VariableName.STRAIN);
			BLAM.add(-1.0, epsilonOld, epsilon);
			BLAM.multiply(1.0, BLAM.NO_TRANSPOSE, C, epsilon, 1.0, sigma);
		}
	}

	@Override
	public void FinalizeSolutionStep(Element e) {
		for (Variable v : getVariables(e)) {
			v.save();
		}
	}

	@Override
	public void CalculateOutputData(Element e) {
		/*
		 * TODO
		 */
	}

	@Override
	public IMatrix getConstitutiveMatrix(Element e, int k) {
		return getVariables(e).get(k).getMatrixVariable(VariableName.TANGENT_OPERATOR);
	}

}
